package com.commissarxiii.loader 
{
	import flash.events.Event;
	import flash.events.IOErrorEvent;
	import flash.external.ExternalInterface;
	import flash.net.URLRequest;
	/**
	 * LoaderQueue handles the loading of multiple
	 * LoaderItems.
	 * 
	 * @author Benjamin Van Heest
	 * @version 1.0
	 */
	public class LoaderQueue 
	{
		/**
		 * LoaderItems to be loaded
		 */
		private var _items:Vector.<LoaderItem>;
		/**
		 * Number of items that have completed loading
		 */
		private var _loadedItems:int;
		/**
		 * Number of items in the queue at the time of loading
		 */
		private var _totalItems:int;
		/**
		 * Indicates the loading status of the queue
		 */
		private var _loading:Boolean;
		/**
		 * Maximum number of simultaneous loads
		 */
		private var _maxConcurrent:int;
		/**
		 * Number of items currently loading
		 */
		private var _activeItems:int;
		/**
		 * Javascript method to be called when the queue has finished loading
		 */
		private var _jsCallback:String;
		
		/**
		 * Create a new LoaderQueue instance.
		 */
		public function LoaderQueue() 
		{
			init();
		}
		
		/**
		 * Initialize LoaderQueue.
		 */
		private function init():void
		{
			resetQueue();
		}
		
		/**
		 * Adds a LoaderItem instance to the queue.
		 * @param	item LoaderItem instance
		 */
		public function addItem(item:LoaderItem):void
		{
			if (!_loading)
			{
				_items.push(item);
			}
		}
		
		/**
		 * Loads all LoaderItem instances in the queue.
		 * 
		 * @param	jsCallback JavaScript method called when the entire queue has loaded
		 * @param	maxConcurrent Maximum concurrent loads. Use 1 for synchronous queue loading or a higher value for
		 * 			asynchronous loading. Caution: A high number of concurrent loads may affect performance.
		 */
		public function load(jsCallback:String = null, maxConcurrent:int = 1):void
		{
			if (!_loading)
			{
				_jsCallback = jsCallback;
				_maxConcurrent = Math.max(maxConcurrent, 1);
				_totalItems = _items.length;
				loadNext();
			}
		}
		
		/**
		 * Attempts to load the next item in the queue. If all items have been loaded,
		 * the queue callback method is called. If the maximum number of concurrent
		 * loads has been reached, loading stops until an item is finished loading.
		 */
		private function loadNext():void
		{
			if (_loadedItems < _totalItems)
			{
				for (var i:int = 0; i < _items.length; i++)
				{
					_activeItems++;
					var item:LoaderItem = _items.shift();
					item.addEventListener(Event.COMPLETE, handleItemComplete);
					item.addEventListener(IOErrorEvent.IO_ERROR, handleItemIOError);
					item.load();
				
					if (_activeItems == _maxConcurrent)
					{
						break;
					}
				}
				
				return;
			}
			
			// queue complete;
			onQueueComplete();
		}
		
		/**
		 * Handles item Complete event. 
		 * @param	event
		 */
		private function handleItemComplete(event:Event):void
		{
			_loadedItems++;
			_activeItems--;
			removeItemListeners(LoaderItem(event.target));
			loadNext();
		}
		
		/**
		 * Handles item IOError event.
		 * @param	event
		 */
		private function handleItemIOError(event:IOErrorEvent):void
		{
			_loadedItems++;
			_activeItems--;
			removeItemListeners(LoaderItem(event.target));
			loadNext();
		}
		
		/**
		 * Called when all items in the queue have loaded.
		 */
		private function onQueueComplete():void
		{
			if (ExternalInterface.available)
			{
				ExternalInterface.call(_jsCallback);
			}
				
			resetQueue();
		}
		
		/**
		 * Resets the queue.
		 */
		private function resetQueue():void
		{
			_items = new Vector.<LoaderItem>();
			_loadedItems = 0;
			_loading = false;
			_jsCallback = '';
			_activeItems = 0;
			_totalItems = 0;
			_maxConcurrent = 1;
		}
		
		/**
		 * Removes listeners from a LoaderItem for memory management.
		 * @param	item
		 */
		private function removeItemListeners(item:LoaderItem):void
		{
			item.removeEventListener(Event.COMPLETE, handleItemComplete);
			item.removeEventListener(IOErrorEvent.IO_ERROR, handleItemIOError);
		}
	}

}